package cn.tedu.charging.order.service;

import cn.tedu.chaging.common.pojo.StationInfoVO;
import cn.tedu.chaging.common.pojo.UserInfoVO;
import cn.tedu.chaging.common.pojo.param.GunStatusUpdateParam;
import cn.tedu.chaging.common.web.JsonResult;
import cn.tedu.charging.order.constant.Constant;
import cn.tedu.charging.order.dao.repository.OrderRepository;
import cn.tedu.charging.order.feign.DeviceClient;
import cn.tedu.charging.order.feign.UserClient;
import cn.tedu.charging.order.mqtt.MqttProducer;
import cn.tedu.charging.order.pojo.DeviceResultParam;
import cn.tedu.charging.order.pojo.dto.CarInfoDTO;
import cn.tedu.charging.order.pojo.dto.ChargingDTO;
import cn.tedu.charging.order.pojo.dto.StationInfoDTO;
import cn.tedu.charging.order.pojo.param.OrderAddParam;
import cn.tedu.charging.order.pojo.po.ChargingBillSuccessPO;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    /**
     * mqtt消息发送工具
     */
    @Autowired
    private MqttProducer mqttProducer;

    /**
     * 数据操作层
     */
    @Autowired
    private OrderRepository orderRepository;


    /**
     * 访问用户服务的feign客户端
     */
    @Autowired
    private UserClient userClient;

    /**
     * 访问设备服务的feign客户端
     */
    @Autowired
    private DeviceClient deviceClient;



    /**
     *
     * @param orderAddParam
     * @return
     *
     * 1 扫码
     * 2 判断余额 余额够 继续
     * 3 创建订单
     * 4 给设备下发指令 开始充电  应该选择哪种通信方式 该怎么告诉设备开始充电
     * 同步
     * 优点
     * 站在发送者的角度  比较简单 好理解
     * 10:01
     *  Boolean success = startCharging() //发生充电指令
     *  if (success) { //判断是否开始充电
     *      10:05
     *      insertOrderData();//创建订单 数据库有记录
     *      return orderNo;
     *  }
     * 缺点
     *
     * 站在接收者的角度
     *      * 设备有多种情况
     *      *  1 没坏 收到了,但是因为网络原因,没有很快响应 告诉接受者
     *      *  2 坏了 导致设备收不到,  没有很快响应 告诉接受者
     *      *
     *      * 发送者不知道设备的状态 是能充还是不能充 等待 页面在转
     *
     * 异步
     *    startCharging();//发生充电指令 发送者
     *    insertOrderData();//创建订单 数据库有记录
     *    return orderNo;
     * 优点
     *
     * 缺点
     * 上面的代码没有实时得到充电桩的状态 能充还是不能充
     *  得有方案来接收 充电桩的状态
     *
     *  接受者
     *  发送完充电指令后,继续执行后面的业务逻辑,当充电桩有响应
     *   正常 开始充电
     *   异常 设备故障  无响应
     *  做后续的处理
     *    开始充电 计费
     *    设备故障  无响应 订单取消,通知用户
     *
     *  和设备通信  MQTT 物联网技术  类似与消息队列  异步和解耦
     *
     *  5 创建订单
     *  6 创建订单编号 唯一  能不能用数据库的自增主键?  1 2 3
     *      不用数据库主键 分库
     *      雪花算法  自己了解 分布式的id生成器
     *      UUID 没有顺序,mysql是建议有顺序 , uuid + 时间戳
     *      自定义   城市编号 + 随机数 + 时间
     *
     *  7  通过枪id获取运营商信息和充电站信息
     *  8  通过用户id获取用户车辆信息
     *  9  通过枪的id修改枪的状态为已使用 充电的时候修改为已经使用,充完后要把枪的状态修改为未使用(空闲)
     *  10 插入订单数据
     *  11 返回订单编号
     *
     *
     *
     */
    @Override
    public String createOrder(OrderAddParam orderAddParam) {
        //todo 订单创建的流程 自定向下编程
        // 1 创建订单编号
        String orderNo = getOrderNo();
        log.debug("生成订单号:{}",orderNo);
        // 2 通过枪id获取运营商信息和充电站信息
        log.debug("获取运营商和场站信息-入参:{}",orderAddParam.getGunId());
        StationInfoDTO stationInfoDto = getStationInfo(orderAddParam.getGunId());
        log.debug("获取运营商和场站信息-入参:{},出参:{}",orderAddParam.getGunId(),stationInfoDto);
        // 3 通过用户id获取用户车辆信息
        log.debug("获取用户车辆信息-入参:{}",orderAddParam.getUserId());
        CarInfoDTO carInfoDto = getCarInfo(orderAddParam.getUserId());
        log.debug("获取用户车辆信息-入参:{},出参:{}",orderAddParam.getUserId(),carInfoDto);
        // 4 通过枪的id修改枪的状态为已使用
        //2表示已使用 就传2
        log.debug("设置充电枪状态为已经使用-入参:枪id-{},状态-{}",orderAddParam.getGunId(),Constant.GUN_STATS_BUSY);
        Boolean success = updateGunStatus(orderAddParam.getGunId(), Constant.GUN_STATS_BUSY);// 2 魔数
        log.debug("设置充电枪状态为已经使用-入参:枪id-{},状态-{},出参:{}",orderAddParam.getGunId(),Constant.GUN_STATS_BUSY,success);
        // 1表示空闲
        //updateGunStatus(orderAddParam.getGunId(),1);

        //updateGunStatusOff(orderAddParam.getGunId());
        //updateGunStatusOn(orderAddParam.getGunId());
        if (!success) {
            log.warn("修改枪状态失败,枪id:{},状态:{}",orderAddParam.getGunId(),Constant.GUN_STATS_BUSY);
        }
        //5 给充电桩发送充电指令
        startCharging(orderNo, orderAddParam.getPileId(), orderAddParam.getGunId());
        //6 充电开始后,
        // 正常情况 设备会定时和订单服务同步数据,温度,电压,用电度数
        // 异常情况 设备接受到开始充电指令后,开始充电,充了一会,设备故障,不会再和订单服务同步数据
        // 订单需要处理这种数据
        // 1 延迟消息
        // 2 定时任务
        return orderNo;
    }




    /**
     * 保存成功订单
     */
    @Override
    public void saveSuccessOrder(DeviceResultParam deviceResultParam) {
        ChargingBillSuccessPO successPO = new ChargingBillSuccessPO();
        successPO.setBillId(deviceResultParam.getOrderNo());
        orderRepository.saveSuccessOrder(successPO);
    }

    @Override
    public void saveFailOrder() {

    }

    @Override
    public List<ChargingBillSuccessPO> orderList(Integer userId) {
        List<ChargingBillSuccessPO> chargingBillSuccessPOS =
                orderRepository.orderList(userId);
        return chargingBillSuccessPOS;
    }

    /**
     * 异步下发充电指令给充电桩
     * @param orderNo 订单号
     * @param pileId 桩id
     * @param gunId 枪id
     */
    // 方法定义入参的编码思想
    //private void startCharging(OrderAddParam orderAddParam) {}




    private void startCharging(String orderNo,
                               Integer pileId,
                               Integer gunId) {
        log.debug("发送指令:订单号-{},桩id-{},枪id-{}",orderNo,pileId,gunId);
        ChargingDTO chargingDto = new ChargingDTO();
        chargingDto.setGunId(gunId);
        chargingDto.setPileId(pileId);
        //开始充电的指令  指令应该越小越好
        // msg表示的是给充电桩发送的指令,有多个,比如start 开始 stop 结束
        chargingDto.setMsg(Constant.START_CHARGING);
        chargingDto.setOrderNo(orderNo);
        log.debug("发送充电指令-入参:{}",chargingDto);
        mqttProducer.send(chargingDto);

    }

    /**
     * 更新枪的状态
     * @param gunId
     * @param status
     * @return
     */
    private Boolean updateGunStatus(Integer gunId, Integer status) {
        GunStatusUpdateParam param = new GunStatusUpdateParam();
        param.setId(gunId);
        param.setStatus(status);
        JsonResult<Boolean> jsonResult = deviceClient.updateStationStatus(param);
        if (jsonResult != null) {
            return jsonResult.getData();
        }
        return false;
    }



    /**
     * 通过feign的方式调用用户服务
     * @param userId
     * @return
     */
    private CarInfoDTO getCarInfoByFeign(Long userId){
        JsonResult<UserInfoVO> userInfo = userClient.getUserInfo(userId);
        if (userInfo != null) {
            UserInfoVO data = userInfo.getData();
            if (data != null) {
                CarInfoDTO carInfoDto = new CarInfoDTO();
                BeanUtils.copyProperties(data, carInfoDto);
                return carInfoDto;
            }
        }
        return null;
    }


    @Autowired
    private RestTemplate restTemplate;


    /**
     * 通过随机算法实现负载均衡
     * @return
     */
    public String chooseUrl(){
        //已知有3个用户服务
        // 动态感知服务的变化 包括上线和上线 前提是订单服务不需要重新启动
        // 注册中心  /  服务发现
        //List<String> servers = nocos.getUrl("userService")
        // random 选中一个
        // feign
        String url = "http://localhost:8083/user/info/{1}"; //占位符号
        String url1 = "http://localhost:8084/user/info/{1}"; //占位符号
        String url2 = "http://localhost:8085/user/info/{1}"; //占位符号
        String url3 = "http://localhost:8086/user/info/{1}";
        String url4 = "http://localhost:8086/user/info/{1}";

        //负载均衡算法 简单实现随机
        //ArrayList 和数组有什么区别? 自己去实现基于ArrayList

        String[] servers = new String[3];
        servers[0] = url;
        servers[1] = url1;
        servers[2] = url2;
        Random random = new Random();
        int r = random.nextInt(servers.length);
        String server = servers[r];
        return server;
    }

    /**
     * 通过用户id获取用户绑定的车辆信息
     * 先假定一个用户只能绑定一辆车 充电的时候就是用户绑定的那辆唯一的车
     * 如果后续功能迭代 充电的时候由用户选择给哪辆车充电, 车的id可以通过入参的方式传入
     * @param userId 高内聚 单一职责 导致 低耦合
     * @return
     */
    private CarInfoDTO getCarInfo(Long userId) {
        return getCarInfoByFeign(userId);
    }

    /**
     * 通过RestTemplate 来调用用户服务
     * @param userId
     * @return
     */
    private CarInfoDTO getCarInfoByRestTemplate(Integer userId) {
        String url = chooseUrl();
        log.debug("通过restTemplate调用用户接口获取用户信息-获取url:{}",url);
        //String url = "http://localhost:8083/user/info/"+userId;
        log.debug("通过restTemplate调用用户接口获取用户信息-入参:{}",userId);
        ParameterizedTypeReference<JsonResult<UserInfoVO>> reposeBody =
                new ParameterizedTypeReference<JsonResult<UserInfoVO>>() {
                };
        ResponseEntity<JsonResult<UserInfoVO>> entity =
                restTemplate.
                        exchange(url,
                                HttpMethod.GET,
                                null,
                                reposeBody,
                                userId);
        log.debug("通过restTemplate调用用户接口获取用户信息-入参:{},出参:{}",userId,entity);

        if(entity != null) {
            HttpStatus statusCode = entity.getStatusCode();
            if (HttpStatus.OK == statusCode) {
                JsonResult<UserInfoVO> body = entity.getBody();
                UserInfoVO data = body.getData();
                log.debug("通过restTemplate调用用户接口获取用户信息-入参:{},body:{}",userId,body);
                CarInfoDTO carInfoDto = new CarInfoDTO();
                Integer carId = data.getCarId();
                carInfoDto.setCarId(carId); //如果参数少可以同手动设置
                BeanUtils.copyProperties(data,carInfoDto);
                return carInfoDto;
            }
        }
/*
        CarInfoDto carInfoDto = new CarInfoDto();
        carInfoDto.setCarId(15);
        return carInfoDto;*/
        return null;
    }


    /**
     * 通过枪id获取运营商信息和充电站信息
     * @param gunId
     * @return StationInfoDto
     */
    private StationInfoDTO getStationInfo(Integer gunId) {
        JsonResult<StationInfoVO> stationInfo = deviceClient.getStationInfo(gunId);
        if (stationInfo != null) {
            StationInfoVO data = stationInfo.getData();
            if (data != null) {
                StationInfoDTO dto = new StationInfoDTO();
                BeanUtils.copyProperties(data,dto);
                return dto;
            }
        }
        return null;
    }

    /**
     * 生成订单号
     *  固定开始的 10000 + _ + 随机数 + _ + 时间戳
     * @return
     */
    private String getOrderNo() {
        //换一种算法 雪花
        String start = "10000";
        long now = System.currentTimeMillis();
        Random random = new Random();
        int r = random.nextInt(100);
        String orderNo =  start + "_" + r + "_" + now;
        return orderNo;
    }

}
